using System.IO;
using System.Threading.Tasks;
using AElf.Kernel.Blockchain.Application;
using AElf.Kernel.Miner.Application;
using AElf.Kernel.SmartContract.Application;
using AElf.Kernel.SmartContract.Infrastructure;
using AElf.Kernel.SmartContract.Parallel.Domain;
using AElf.Kernel.SmartContractExecution.Parallel.Tests.TestContract;
using AElf.Modularity;
using AElf.OS;
using AElf.Runtime.CSharp;
using AElf.Types;
using Google.Protobuf;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
using CleanBlockExecutedDataChangeHeightEventHandler =
    AElf.Kernel.SmartContract.Parallel.Application.CleanBlockExecutedDataChangeHeightEventHandler;
using ServiceDescriptor = Google.Protobuf.Reflection.ServiceDescriptor;

namespace AElf.Kernel.SmartContract.Parallel.Tests;

public class ParallelMockTestModule : AElfModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var services = context.Services;
        services.AddSingleton<IResourceExtractionService, ResourceExtractionService>();
        services.AddTransient<ParallelExecutionInterestedEventsHandler>();
        var executiveService = GetSmartContractExecutiveService(
            (InternalConstants.NonAcs2, GetNonAcs2Executive()),
            (InternalConstants.Acs2, GetAcs2Executive()), (InternalConstants.NonParallel, GetNonParallelExecutive())
        );
        services.AddSingleton(executiveService);
        context.Services.AddSingleton(
            _ =>
            {
                var mock = new Mock<IBlockchainService>();
                mock.Setup(s => s.GetChainAsync()).Returns(Task.FromResult(new Chain
                {
                    BestChainHash = Hash.Empty
                }));
                return mock.Object;
            });
        context.Services.AddSingleton(
            _ =>
            {
                var mock = new Mock<INonparallelContractCodeProvider>();
                mock.Setup(s =>
                        s.GetNonparallelContractCodeAsync(It.IsAny<IChainContext>(),
                            It.Is<Address>(address => address == Address.FromBase58(InternalConstants.NonParallel))))
                    .Returns(Task.FromResult(new NonparallelContractCode
                    {
                        CodeHash = GetNonParallelExecutive().ContractHash
                    }));
                mock.Setup(s =>
                        s.GetNonparallelContractCodeAsync(It.IsAny<IChainContext>(),
                            It.Is<Address>(address => address != Address.FromBase58(InternalConstants.NonParallel))))
                    .Returns(Task.FromResult((NonparallelContractCode)null));
                return mock.Object;
            });

        context.Services.AddTransient<ITransactionContextFactory, ParallelTestTransactionContextFactory>();
    }

    #region Mocks

    #region NonAcs2

    private static IExecutive GetNonAcs2Executive()
    {
        var emptyServiceDescriptors = new ServiceDescriptor[0];
        var executive = new Mock<IExecutive>();
        executive.SetupGet(e => e.Descriptors).Returns(emptyServiceDescriptors);
        return executive.Object;
    }

    #endregion

    #region Acs2

    private static IExecutive GetAcs2Executive()
    {
        var testContractFile = typeof(TestContract).Assembly
            .Location;
        var code = File.ReadAllBytes(testContractFile);
        var runner = new CSharpSmartContractRunner(
            Path.GetDirectoryName(testContractFile)
        );
        var executive = AsyncHelper.RunSync(() => runner.RunAsync(new SmartContractRegistration
        {
            Category = 0,
            Code = ByteString.CopyFrom(code),
            CodeHash = HashHelper.ComputeFrom(code)
        }));
        executive.SetHostSmartContractBridgeContext(Mock.Of<IHostSmartContractBridgeContext>());
        return executive;
    }

    #endregion

    private static IExecutive GetNonParallelExecutive()
    {
        var testContractFile = typeof(TestContract).Assembly
            .Location;
        var code = File.ReadAllBytes(testContractFile);
        var runner = new CSharpSmartContractRunner(
            Path.GetDirectoryName(testContractFile)
        );
        var executive = AsyncHelper.RunSync(() => runner.RunAsync(new SmartContractRegistration
        {
            Category = 0,
            Code = ByteString.CopyFrom(code),
            CodeHash = HashHelper.ComputeFrom(code)
        }));
        executive.SetHostSmartContractBridgeContext(Mock.Of<IHostSmartContractBridgeContext>());
        return executive;
    }

    private static ISmartContractExecutiveService GetSmartContractExecutiveService(
        params (string, IExecutive)[] named)
    {
        var executiveService = new Mock<ISmartContractExecutiveService>();
        foreach (var tuple in named)
            executiveService.Setup(
                s => s.GetExecutiveAsync(It.IsAny<IChainContext>(),
                    It.Is<Address>(address => address == Address.FromBase58(tuple.Item1)))
            ).Returns(Task.FromResult(tuple.Item2));

        return executiveService.Object;
    }

    #endregion
}

[DependsOn(
    typeof(OSCoreWithChainTestAElfModule),
    typeof(ParallelExecutionModule)
)]
public class ParallelExecutionTestModule : AElfModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services
            .AddSingleton<CleanBlockExecutedDataChangeHeightEventHandler>();
        context.Services.AddSingleton<ISystemTransactionExtraDataProvider, MockSystemTransactionExtraDataProvider>();
    }
}